#include "./CEC2013/cec2013.h"
#include "Self_Define_Functions.h"
#include <sys/time.h>
#include <cstdio>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <stdlib.h>
#include <iomanip>
#include <string>
#include <boost/random.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/cauchy_distribution.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real.hpp>



using namespace std;


// in this program the global best solution is updated after each subgroup finish iterating itself
int main(int argc, char *argv[])
{

    int funToRun[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};  //function set
    //int funToRun[] = {6,7,8,9,10};
    //int funToRun[] = {11,12,13,14,15};
    //int funToRun[] = {16,17,18,19,20};
    int funNum = 20; //total function num

    int population_size_set[] = {80,80,80,80,80,100,300,300,300,100,200,200,200,200,200,200,200,200,200,200};

    int neighbor_size_set[] = {2,4,5,6,8,10};

    int neighbor_size_set_size;


    int i,j,k,fun,run;
    CEC2013 *pFunc = NULL;
    int population_size;
    int dim;
    int Fes;
    int MAX_FES;
    int temp_global_optima_size;
    int global_optima_num;
    int N_nearest;//neighbour size
    int last_species_size;

    int record_count=0;

    int **runs_global_num = new int*[epsilon_set_size];
    int **runs_used_fitness = new int*[epsilon_set_size];

    int record_size = 10;

    int *record_fes = new int[record_size];

    int ***runs_global_num_vs_fes = new int**[epsilon_set_size];

    bool *epsilon_flag = new bool[epsilon_set_size];

    for( i = 0; i < epsilon_set_size; ++i )
    {
        runs_global_num[i] = new int [timesOfRun];
        runs_used_fitness[i] = new int[timesOfRun];
        runs_global_num_vs_fes[i] = new int*[timesOfRun];

        for( j = 0; j < timesOfRun; ++j )
        {
            runs_global_num_vs_fes[i][j] =  new int[record_size];
        }
    }

    int species_num;

    double std_factor = 1.0;

    double cauchy_gaussian_prob = 0.5;

    double local_std_value = 1e-4;

    int local_sample_num = 5;






    for ( fun =0; fun <funNum; fun ++)
    {
        cout<<"Function "<<funToRun[fun]<<" Begined!"<<endl;

        pFunc = new CEC2013(funToRun[fun]);

        dim = pFunc->get_dimension();

        global_optima_num = pFunc->get_no_goptima();

        MAX_FES = pFunc->get_maxfes();


        double *LBound = new double[dim];
        double *UBound = new double[dim];

        for( i = 0; i < dim; ++i )
        {
            LBound[i] = pFunc->get_lbound(i);
            UBound[i] = pFunc->get_ubound(i);

        }


        for( i = 0; i < record_size; ++i )
        {
            record_fes[i] = (int)((double(i+1))/record_size * MAX_FES);
        }

        population_size = population_size_set[funToRun[fun]-1];
        if( population_size <= 100 )
            neighbor_size_set_size = 4;
        else
            neighbor_size_set_size = 6;

        double **population = new double*[population_size];
        double **dist = new double*[population_size];
        double **child = new double*[population_size];
        int **species = new int*[population_size];
        for( i = 0; i < population_size; ++i )
        {
            population[i] = new double[dim];
            child[i] = new double[dim];
            dist[i] = new double[population_size];
            species[i] = new int[ population_size];
        }


        double *population_results = new double [population_size];
        double *child_results = new double[population_size];

        double *reference_point = new double[dim];


        int *seed_location = new int [population_size];
        double *species_local_prob = new double [population_size];



        Fes = 0;

        // to initialize the population
        boost::mt19937 generator(time(0)*rand());
        boost::uniform_real<> uniform_real_generate_r( 0, 1 );
        boost::variate_generator< boost::mt19937&, boost::uniform_real<> > random_real_num_r( generator, uniform_real_generate_r );

        boost::uniform_int<> uniform_int_generator( 0, neighbor_size_set_size-1 );
        boost::variate_generator< boost::mt19937&, boost::uniform_int<> > random_int_num( generator, uniform_int_generator);




        for (run = 0; run < timesOfRun; run++)
        {
            cout<<"Running the "<<run<<"th times"<<endl;

            for( i = 0; i < epsilon_set_size; ++i )
            {
                epsilon_flag[i] = false;
            }

            Fes = 0;
            record_count=0;

            // initialize the population
            for( j =0; j < dim; ++j )
            {
                boost::uniform_real<> uniform_real_generate_x( LBound[j], UBound[j] );
                boost::variate_generator< boost::mt19937&, boost::uniform_real<> > random_real_num_x( generator, uniform_real_generate_x );

                for( i =0 ; i < population_size; ++i )
                {
                    population[i][j] = random_real_num_x();
                }
            }

            Popupation_Fitness( population, population_size, Fes, population_results, pFunc );

            Dist_All_to_All( population, dist, population_size, dim );


            while( Fes < MAX_FES )
            {
                //compute the number of global optima found
                for( i = 0; i < epsilon_set_size; ++i )
                {
                    if( !epsilon_flag[i] )
                    {
                        temp_global_optima_size = Compute_Global_Optima_Found( population,  population_results, population_size, dim, epsilon_set[i], pFunc );
                        runs_global_num[i][run] = temp_global_optima_size;
                        runs_used_fitness[i][run] = Fes;
                        if( temp_global_optima_size == global_optima_num )
                        {
                            epsilon_flag[i] = true;
                        }
                    }
                }

                if( Fes >= record_fes[record_count] )
                {
                    for( i = 0; i < epsilon_set_size; ++i )
                    {
                        runs_global_num_vs_fes[i][run][record_count] = runs_global_num[i][run];
                    }

                    record_count++;
                }

                N_nearest = neighbor_size_set[ random_int_num() ];

                species_num = population_size / N_nearest;

                if( population_size % N_nearest == 0 )
                {
                    last_species_size = 0;
                }
                else
                {
                    species_num++;
                    last_species_size = population_size % N_nearest;
                }




                Generate_Reference_Point( reference_point, LBound, UBound, dim );

                Species( reference_point, population, species, dist, species_num, N_nearest, last_species_size, population_size, dim );

                //evlove
                Evolve_Gaussian( species, population, child, LBound, UBound, dim, species_num, N_nearest, last_species_size, std_factor );

                Popupation_Fitness( child, population_size, Fes, child_results, pFunc );

                Selection( population, child, dist, population_results, child_results, population_size, dim );

                Get_Seed_Location( seed_location, population_results, species, species_num, N_nearest, last_species_size );

                Seed_Local_Prob( species_local_prob, seed_location, population_results, species_num );

                Local_Evolve( population, population_results, LBound, UBound, species_local_prob, seed_location, dim, species_num, local_std_value, Fes, pFunc, local_sample_num );




            }

            for( i = 0; i < epsilon_set_size; ++i )
            {
                if( !epsilon_flag[i] )
                {
                    temp_global_optima_size = Compute_Global_Optima_Found( population, population_results, population_size, dim, epsilon_set[i], pFunc );

                    runs_global_num[i][run] = temp_global_optima_size;
                    runs_used_fitness[i][run] = MAX_FES;

                }
            }

            for( i = 0; i < epsilon_set_size; ++i )
            {
                runs_global_num_vs_fes[i][run][record_count] = runs_global_num[i][run];
            }


        }


        char fun_name[10];
        char epsilon_counter[10];
        snprintf(fun_name,10,"%d",funToRun[fun]);

        string *Epsilon_Files = new string[epsilon_set_size];
        ofstream *out_Epsilon = new ofstream[epsilon_set_size];

        for( i = 0; i < epsilon_set_size; ++i )
        {
            snprintf(epsilon_counter,10,"%d",i+1);
            Epsilon_Files[i] = "./Results2/"+string(epsilon_counter)+"/"+"Final_Optima_Num_And_FES_Epsilon_"+string(epsilon_counter)+"_Fun_"+ string(fun_name)+".txt";
        }



        for( i = 0; i < epsilon_set_size; ++i )
        {
            out_Epsilon[i].open( Epsilon_Files[i].c_str() );
        }




        for( i = 0; i < epsilon_set_size; ++i )
        {
            for( j = 0; j < timesOfRun; ++j )
            {
                out_Epsilon[i]<<runs_global_num[i][j] <<"\t"<<runs_used_fitness[i][j]<<endl;
            }
        }



        for( i = 0; i < epsilon_set_size; ++i )
        {
            out_Epsilon[i].close();
        }

        for( i = 0; i < epsilon_set_size; ++i )
        {
            snprintf(epsilon_counter,10,"%d",i+1);
            Epsilon_Files[i] = "./Results2/"+string(epsilon_counter)+"/"+"Optima_Num_VS_FES_Epsilon_"+string(epsilon_counter)+"_Fun_"+ string(fun_name)+".txt";
        }



        for( i = 0; i < epsilon_set_size; ++i )
        {
            out_Epsilon[i].open( Epsilon_Files[i].c_str() );
        }

        for( i = 0; i < epsilon_set_size; ++i )
        {
            for( j = 0; j < timesOfRun; ++j )
            {
                for( k = 0; k < record_size; ++k )
                    out_Epsilon[i]<<runs_global_num_vs_fes[i][j][k] <<"\t";
                out_Epsilon[i]<<endl;
            }
        }

        for( i = 0; i < epsilon_set_size; ++i )
        {
            out_Epsilon[i].close();
        }



        cout<<"Function "<<funToRun[fun]<<" Finished!"<<endl;


        //release the resouces
        for( i =0; i < population_size; ++i )
        {
            delete []population[i];
            delete []dist[i];
            delete []child[i];
            delete []species[i];
        }
        delete []population;
        delete []dist;
        delete []child;
        delete []species;


        delete []population_results;
        delete []child_results;
        delete []reference_point;
        delete []LBound;
        delete []UBound;
        delete []species_local_prob;
        delete []seed_location;

        delete []Epsilon_Files;
        delete []out_Epsilon;



	}


    for( i = 0; i < epsilon_set_size; ++i )
    {
        delete []runs_global_num[i];
        delete []runs_used_fitness[i];
        for( j = 0; j < timesOfRun; ++j )
        {
            delete []runs_global_num_vs_fes[i][j];
        }
        delete []runs_global_num_vs_fes[i];

    }

    delete []runs_global_num;
    delete []runs_used_fitness;
    delete []epsilon_flag;
    delete []runs_global_num_vs_fes;

    delete []record_fes;


    delete pFunc;

    return 0;
}



